package edu.pitt.cs.cs2510.project3.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;

import org.json.*;


public class InputParameters {

	final String srcFile;
	final JSONObject json;
	
	public InputParameters(String inputFilenameJson) {
		srcFile = inputFilenameJson;
		srcFile.length(); //will throw NullPointerException for me if it is null
		json = readJsonFile(srcFile);
		if(json == null)
			throw new IllegalArgumentException("The JSON file specified, " + srcFile + ", could not be parsed.");
	}

	public int getFileCount()			{ return (int)getParam("fileCount"); }
	public int getBlocksPerFile()		{ return (int)getParam("blocksPerFile"); }
	public int getStorageSize()			{ return (int)getParam("storageSize"); }
	public int getReplicationCost()		{ return (int)getParam("replicationCost"); }
	public double getBandwidthMultiple(){ return getParam("bandwidthMultiple"); }
	public double getMessageWeight()	{ return getParam("messageWeight"); }
	public int getBlockConsumeTime() { return (int)getParam("blockConsumptionTime"); }
	public int getStaticFrontendCount() { return (int)getParam("staticFrontEndCount"); }
	public double getHighLoadThreshold(){ return getParam("highLoadThreshold"); }
	public double getLowLoadThreshold()	{ return getParam("lowLoadThreshold"); }
	public double getOverLoadThreshold(){ return getParam("overloadThreshold"); }
	public double getReplicateHotness() { return getParam("replicationHotness"); }
	public double getDeleteHotness() 	{ return getParam("deletionHotness"); }
	public int getHotnessInterval() 	{ return (int)getParam("hotnessInterval"); }
	public int getLagCost() 			{ return (int)getParam("lagCost"); }
	public int getUptimeCost() 			{ return (int)getParam("uptimeCost"); }
	
	
	
	public int getBufferSize(int clientId) {
		// TODO Add to config file
		return 10;
	}

	public int getFrontendCount() {
		try {
			return json.getJSONArray("FRONTENDS").length();
		} catch (JSONException e) {throw new RuntimeException(e.toString(), e);}
	}
	public int[] getFilesAtFrontend(int frontendIndex) {
		if(cache_filesAtFrontend == null)
			cache_filesAtFrontend = new int[getFrontendCount()][];
		else if(cache_filesAtFrontend[frontendIndex] != null)
			return cache_filesAtFrontend[frontendIndex];
		try {
			JSONObject frontEnds = json.getJSONArray("FRONTENDS").getJSONObject(frontendIndex);
			JSONArray frontEndFiles = frontEnds.getJSONArray("files");
			int[] ret = new int[frontEndFiles.length()];
			for(int i=0; i<ret.length; i++)
				ret[i] = frontEndFiles.getInt(i);
			cache_filesAtFrontend[frontendIndex] = ret;
			return ret;
		} catch (JSONException e) {throw new RuntimeException(e.toString(), e);}
	}
	private int[][] cache_filesAtFrontend = null;

	
	public int getClientCount() {
		try {
			return json.getJSONArray("CLIENTS").length();
		} catch (JSONException e) {throw new RuntimeException(e.toString(), e);}
	}
	
	public int getFrontendDistance(int frontEndIndex1, int frontEndIndex2) {
		if(cache_frontEnd_distance != null)
			return cache_distance[frontEndIndex1][frontEndIndex2];
		try {
			cache_distance = new int[getClientCount()][getFrontendCount()];
			for(int i=0; i<cache_distance.length; i++) {
				JSONArray ci = json.getJSONArray("FRONTENDS").getJSONObject(i).getJSONArray("distances");
				int[] hops = new int[ci.length()];
				if(hops.length != getFrontendCount()) throw new RuntimeException(
						"Config file does not define correct number of server-server hops for SERVER i="+i+
						".  Expected " + getFrontendCount() + " but found " + hops.length);
				for(int j=0; j<hops.length; j++)
					hops[j] = ci.getInt(j);
				cache_distance[i] = hops;
			}
			return getFrontendDistance(frontEndIndex1, frontEndIndex2);
		} catch (JSONException e) {throw new RuntimeException(e.toString(), e);}
	}
	private int[][] cache_frontEnd_distance = null;
	
	public int getDistance(int clientIndex, int frontendIndex) {
		if(cache_distance != null)
			return cache_distance[clientIndex][frontendIndex];
		try {
			cache_distance = new int[getClientCount()][getFrontendCount()];
			for(int i=0; i<cache_distance.length; i++) {
				JSONArray ci = json.getJSONArray("CLIENTS").getJSONArray(i);
				int[] hops = new int[ci.length()];
				if(hops.length != getFrontendCount()) throw new RuntimeException(
						"Config file does not define correct number of client-server hops for CLIENT i="+i+
						".  Expected " + getFrontendCount() + " but found " + hops.length);
				for(int j=0; j<hops.length; j++)
					hops[j] = ci.getInt(j);
				cache_distance[i] = hops;
			}
			return getDistance(clientIndex, frontendIndex);
		} catch (JSONException e) {throw new RuntimeException(e.toString(), e);}
	}
	private int[][] cache_distance = null;

	public int getMaxRequestTime() {
		if(cache_maxRequestTime < 0) {
			try {
				JSONArray reqs = json.getJSONArray("REQUESTS");
				for(int i=0; i<reqs.length(); i++)
					if(reqs.getJSONArray(i).getInt(2) > cache_maxRequestTime)
						cache_maxRequestTime = reqs.getJSONArray(i).getInt(2);
			} catch (JSONException e) { throw new RuntimeException(e.toString(), e); }
		}
		return cache_maxRequestTime;
	}
	private int cache_maxRequestTime = -1;
	
	public RequestParameters[] getRequests(int time) {
		if(cache_requestParameters.containsKey(time))
			return cache_requestParameters.get(time);
		try {
			JSONArray r, reqs = json.getJSONArray("REQUESTS");
			ArrayList<RequestParameters> ret = new ArrayList<RequestParameters>();
			for(int i=0; i<reqs.length(); i++)
				if((r = reqs.getJSONArray(i)).getInt(2) == time) 
					ret.add(new RequestParameters(r.getInt(0),r.getInt(1),r.getInt(2)));
			cache_requestParameters.put(time, ret.toArray(new RequestParameters[ret.size()]));
			return cache_requestParameters.get(time);
		} catch (JSONException e) { throw new RuntimeException(e.toString(), e); }
	}
	private HashMap<Integer, RequestParameters[]> cache_requestParameters = new HashMap<Integer, RequestParameters[]>();
	
	private double getParam(String name) {
		String prm = "";
		try {
			prm = ((JSONObject)json.get("PARAMS")).get(name).toString().trim();
			return Double.parseDouble(prm);
		} 
		catch (JSONException e) { throw new RuntimeException("unhandled json error. " + e.toString(), e);	}
		catch(NumberFormatException e) { throw new RuntimeException(name + " could not be parsed as a number: " + prm);	}		
	}
	
	
	public String toString() {
		return "InputParameters [Source=" + srcFile + ", getFileCount()="
				+ getFileCount() + ", getBlocksPerFile()=" + getBlocksPerFile()
				+ ", getStorageSize()=" + getStorageSize()
				+ ", getReplicationCost()=" + getReplicationCost()
				+ ", getBandwidthMultiple()=" + getBandwidthMultiple()
				+ ", getMessageWeight()=" + getMessageWeight()
				+ ", getFrontendCount()=" + getFrontendCount()
				+ ", getClientCount()=" + getClientCount()
				+ ", getMaxRequestTime()=" + getMaxRequestTime() + "]";
	}

	
	static JSONObject readJsonFile(String filename) {
		byte[] buf;
		try {
			int len = (int)(new File(filename).length());
			buf = new byte[len];
			FileInputStream fis = new FileInputStream(filename);
			fis.read(buf);
			fis.close();
		}
		catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		
		try {
			JSONObject ret = new JSONObject(new String(buf, Charset.defaultCharset()));
			return ret;
		} catch (JSONException e) {
			e.printStackTrace();
			return null;
		}
	}


}
